package qingluan

import (
	"fmt"
	"strings"
	"errors"
	"strconv"

	log "github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
	"github.com/docker/go-plugins-helpers/network"
	
)


const (
	PluginDataDir  = "/var/lib/docker-pipe-network/metadata/"
	DefaultNetwork = "nat"
	portMap = "com.docker.network.portmap"
	HostPort = "HostPort"
	Port  = "Port"

	modeNoNAT = "nonat"
	modeNAT   = "nat"
	modeFlat  = "flat"
	modeVlan  = "vlan"
	modeVxlan = "vxlan"
	Prefix    = "eth" 
)


type QingluanNet struct {
	Name 			string
}



type Endpoint struct {
	SrcName 	string
	Address 	string
	Prefix      string
	Gateway     string
	MaPort      []map[string]string
}


type Network struct {
	NetworkID   string
	Vni         string
	Model       string
	Ipam        *network.IPAMData
	Endpoints   map[string]*Endpoint
}

func NewQingluanNet() *QingluanNet {
	log.Debugf("Startup Qingluan network plugin")
	driver := QingluanNet{
		Name: "qingluan",
	}
	return &driver
}

func (driver *QingluanNet) GetCapabilities() (*network.CapabilitiesResponse, error) {
	log.Debugf("GetCapabilities")
	return &network.CapabilitiesResponse{Scope: network.LocalScope}, nil
}
// create network method
func (driver *QingluanNet) CreateNetwork(request *network.CreateNetworkRequest) error {
	log.Debugf("create network")
	var network = Network{
		NetworkID: request.NetworkID[:10],
		Endpoints: make(map[string]*Endpoint),
	}
	network.Ipam = request.IPv4Data[0]
	opt := request.Options["com.docker.network.generic"].(map[string]interface{})
	if len(opt) != 0 {
		if opt["model"] == nil {
			return nil
		}
		if  opt["model"] == "vlan" ||  opt["model"] == "vxlan" {
			if opt["vni"] == nil {
				return nil
			}
			network.Vni = opt["vni"].(string)
		}
		network.Model = opt["model"].(string)
	} else {
		network.Model = DefaultNetwork
	}
	network.WriteEtcd(fmt.Sprintf("network/%s",request.NetworkID))
	if err := driver.createBridge(network.Model, network.NetworkID, network.Ipam.Gateway, network.Vni); err != nil {
		return err
	}
	return nil
}

func (driver *QingluanNet) DeleteNetwork(deleteNetworkRequest *network.DeleteNetworkRequest) error {
	log.Debugf("DeleteNetwork")
	if err := deletePort(FvBridge, "ovs-fv-ovs-" + deleteNetworkRequest.NetworkID[:5]); err != nil {
		return err
	}
	if err := deleteBridge(deleteNetworkRequest.NetworkID); err != nil {
		return err
	}
	network := &Network{}
	network, _ = network.ReadEtcd(fmt.Sprintf("network/%s",deleteNetworkRequest.NetworkID))
	if err := network.DeleteKey(fmt.Sprintf("network/%s",deleteNetworkRequest.NetworkID)); err != nil {
		return err
	}
	return nil
}

func (driver *QingluanNet) CreateEndpoint(createEndpointRequest *network.CreateEndpointRequest) (*network.CreateEndpointResponse, error) {
	log.Debugf("CreateEndpoint")
	response := &network.CreateEndpointResponse{}
	endPointId := createEndpointRequest.EndpointID
	networkId := createEndpointRequest.NetworkID
	ipaddress := createEndpointRequest.Interface.Address
	veth := "veth" + endPointId[:5]
	peer := "peer" + endPointId[:5]

	vethPeer := &netlink.Veth{
		LinkAttrs: netlink.LinkAttrs{Name: peer},
		PeerName:  veth,
	}
	if err := netlink.LinkAdd(vethPeer); err != nil {
		return response, errors.New("[CreateEndpoint] failed to add the veth device")
	}
	if err := netlink.LinkSetUp(vethPeer); err != nil {
		return response, errors.New("[setup] failed to set veth peer up")
	}

	if err := driver.LinkSetMaster("ovs-"+networkId[:5], peer); err != nil {
		return response, nil
	}
	var endpoint = Endpoint{
		SrcName: 	veth,
		Address: 	ipaddress,
		Prefix:     Prefix,
	}
	endpoint.WriteEtcd(fmt.Sprintf("endpoint/%s",endPointId))
	return response, nil
}
func (driver *QingluanNet) DeleteEndpoint(request *network.DeleteEndpointRequest) error {
	log.Debugf("DeleteEndpoint...")
	//1.删除endpoint etcd
	//2.删除veth perr
	//3.删除map port....
	endpointId, networkId := request.EndpointID, request.NetworkID
	endpoint, host, network := &Endpoint{}, &Host{}, &Network{}
	endpoint, _ = endpoint.ReadEtcd(fmt.Sprintf("endpoint/%s",endpointId))
	network, _ = network.ReadEtcd(fmt.Sprintf("network/%s",networkId))
	srcName, mapPort, bridge := endpoint.SrcName, endpoint.MaPort, network.NetworkID
	var hostPort = make([]string, 0, 1)
	for _, item := range mapPort {
		for key, value := range item {
			if key == HostPort {
				hostPort = append(hostPort, value)
			}
		}
	}
	// 1.delete veth perr first.
	deletePort("ovs-"+bridge[:5], "peer"+srcName[4:])
	link, _ := netlink.LinkByName(srcName)
	err := netlink.LinkDel(link)
	if err != nil {
		return errors.New("[DeleteEndpoint] failed to delete interface")
	}
	// 2.delete Host  map port
	host,_ = host.ReadEtcd(portKey)
	if err := host.Delport(portKey, hostPort); err != nil {
		return err
	}
	// 3. 
	if err := endpoint.DeleteKey(fmt.Sprintf("endpoint/%s",endpointId)); err != nil {
		return err
	}	
	return nil
}
func (driver *QingluanNet) EndpointInfo(infoRequest *network.InfoRequest) (*network.InfoResponse, error) {
	log.Debugf("endpoint info...")

	return nil, nil
}
func (driver *QingluanNet) AllocateNetwork(infoRequest *network.AllocateNetworkRequest) (*network.AllocateNetworkResponse, error) {

	log.Debugf("AllocateNetwork...")
	return nil, nil
}
func (driver *QingluanNet) FreeNetwork(infoRequest *network.FreeNetworkRequest) error {
	log.Debugf("FreeNetwork...")
	return nil

}
func (driver *QingluanNet) Join(joinRequest *network.JoinRequest) (*network.JoinResponse, error) {
	log.Debugf("joing....")
	eid := joinRequest.EndpointID
	var endpoint = &Endpoint{}
	endpoint, _ = endpoint.ReadEtcd(fmt.Sprintf("endpoint/%s",eid))
	resp := &network.JoinResponse{}
	resp.InterfaceName.SrcName = endpoint.SrcName
	resp.InterfaceName.DstPrefix = endpoint.Prefix
	nid := joinRequest.NetworkID
	var network = &Network{}
	network, _ = network.ReadEtcd(fmt.Sprintf("network/%s",nid))
	gateway := strings.Split(network.Ipam.Gateway, "/")
	resp.Gateway = gateway[0]
	return resp, nil
}
func (driver *QingluanNet) Leave(leaveRequest *network.LeaveRequest) error {
	log.Debugf("Leave...")
	return nil
}
func (driver *QingluanNet) DiscoverNew(discoveryNotification *network.DiscoveryNotification) error {
	log.Debugf("DiscoverNew...")
	return nil
}
func (driver *QingluanNet) DiscoverDelete(discoveryNotification *network.DiscoveryNotification) error {
	log.Debugf("DiscoverDelete...")
	return nil
}
func (driver *QingluanNet) ProgramExternalConnectivity(request *network.ProgramExternalConnectivityRequest) error {
	log.Debugf("ProgramExternalConnectivity...")
	var mPort = make([]map[string]string, 0, 1)
	if request.Options != nil {
		if maPort, ok := request.Options[portMap].([]interface {}); ok{
			for _, value1 := range(maPort) {
				if value2, ok := value1.(map[string]interface {}); ok {
					var temp = make(map[string]string)
					for host , item := range(value2) {
						if value, ok := item.(interface {}); ok {
							if value3, ok := value.(float64); ok {
								if host == HostPort || host == Port{
									temp[host] = strconv.Itoa(int(value3))
								}
							}
						}
					}
					mPort = append(mPort, temp)
				}
			}
		}
	}

	endpoint := &Endpoint{
		MaPort: make([]map[string]string, 0, 1),
	}
	endpoint, _ = endpoint.ReadEtcd(fmt.Sprintf("endpoint/%s",request.EndpointID))
	conIp := strings.Split(endpoint.Address,"/")[0]
	 if err := mapPort(conIp, request.NetworkID, mPort); err != nil {
	 	return err
	 }

	endpoint.MaPort = mPort
	endpoint.WriteEtcd(fmt.Sprintf("endpoint/%s",request.EndpointID))
	return nil
}
func (driver *QingluanNet) RevokeExternalConnectivity(request *network.RevokeExternalConnectivityRequest) error {
	log.Debugf("RevokeExternalConnectivity...")
	endpointId, networkId := request.EndpointID, request.NetworkID
	if err := delMapPort(networkId, endpointId); err != nil {
		return err
	}
	return nil
}

func getGatewayIP(request *network.CreateNetworkRequest) (string, string, error) {
	// FIXME: Dear future self, I'm sorry for leaving you with this mess, but I want to get this working ASAP
	// This should be an array
	// We need to handle case where we have
	// a. v6 and v4 - dual stack
	// auxilliary address
	// multiple subnets on one network
	// also in that case, we'll need a function to determine the correct default gateway based on it's IP/Mask
	var gatewayIP string

	if len(request.IPv6Data) > 0 {
		if request.IPv6Data[0] != nil {
			if request.IPv6Data[0].Gateway != "" {
				gatewayIP = request.IPv6Data[0].Gateway
			}
		}
	}
	// Assumption: IPAM will provide either IPv4 OR IPv6 but not both
	// We may want to modify this in future to support dual stack
	if len(request.IPv4Data) > 0 {
		if request.IPv4Data[0] != nil {
			if request.IPv4Data[0].Gateway != "" {
				gatewayIP = request.IPv4Data[0].Gateway
			}
		}
	}

	if gatewayIP == "" {
		return "", "", errors.New("No gateway IP found")
	}
	parts := strings.Split(gatewayIP, "/")
	if parts[0] == "" || parts[1] == "" {
		return "", "", errors.New("Cannot split gateway IP address")
	}
	return parts[0], parts[1], nil
}
